﻿using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Core.Framework.Model.WebSockets
{
    public class WebSocketApplication
    {
        /// <summary>
        ///  客户机
        /// </summary>
        public static ConcurrentDictionary<WebSocket, ClientInfo> ClientsPool =
            new ConcurrentDictionary<WebSocket, ClientInfo>();

        /// <summary>
        /// 删除客户机
        /// </summary>
        /// <param name="client"></param>
        public static void ClientRemove(WebSocket client)
        {
            if (ClientsPool.ContainsKey(client))
            {
                ClientsPool.TryRemove(client, out ClientInfo model);
            }
        }

        /// <summary>
        /// 注册客户机
        /// </summary>
        /// <param name="client"></param>
        /// <param name="clientInfo"></param>
        public static void ClientReg(WebSocket client, ClientInfo clientInfo)
        {
            if (!ClientsPool.ContainsKey(client))
            {
                ClientsPool.TryAdd(client, clientInfo);
            }
        }

        /// <summary>
        ///  发送消息
        /// </summary>
        /// <param name="webSocket"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public static async Task SendAsync(WebSocket webSocket, string msg)
        {

            CancellationToken cancellation = default(CancellationToken);

            var buf = Encoding.UTF8.GetBytes(msg);

            var segment = new ArraySegment<byte>(buf);

            await webSocket.SendAsync(segment, WebSocketMessageType.Text, true, cancellation);

        }

        /// <summary>
        ///  发送消息
        /// </summary>
        /// <param name="webSocket"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public static async Task SendAsync(WebSocket webSocket, object msg)
        {
            await SendAsync(webSocket, JsonConvert.SerializeObject(msg));
        }

        /// <summary>
        /// 接受消息
        /// </summary>
        /// <param name="webSocket"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public static async Task RecvAsync(
            WebSocket webSocket, 
            CancellationToken cancellationToken,
            Action<WebSocket, Task<string>, Func<string, WebSocket, Tuple<ClientInfo, bool>>> action, 
            Func<string, WebSocket, Tuple<ClientInfo, bool>> getClientInfoByUserToken,
            Action<WebSocket> OutLogin)
        {

            using (MemoryStream memory = new MemoryStream())
            {

                var buffer = new ArraySegment<byte>(new byte[1024 * 8]);

                WebSocketReceiveResult result;

                // 接受消息
                do
                {

                    cancellationToken.ThrowIfCancellationRequested(); // +

                    result = await webSocket.ReceiveAsync(buffer, cancellationToken);

                    memory.Write(buffer.Array, buffer.Offset, result.Count - buffer.Offset);

                } while (!result.EndOfMessage);

                memory.Seek(0, SeekOrigin.Begin);

                using (StreamReader reader = new StreamReader(memory))
                {

                    // 继续接受消息
                    if (webSocket.State == WebSocketState.Open)
                    {
                        action(webSocket, reader.ReadToEndAsync(), getClientInfoByUserToken);
                        await RecvAsync(webSocket, cancellationToken, action, getClientInfoByUserToken, OutLogin);
                    }
                    else
                    {
                        OutLogin(webSocket);
                    }

                }

            }

        }

        /// <summary>
        /// 消息分支动作
        /// </summary>
        /// <param name="webSocket"></param>
        /// <param name="msg"></param>
        public static async void MessageBranchAction(
            WebSocket webSocket, 
            Task<string> msg, 
            Func<string, WebSocket,Tuple<ClientInfo, bool>> getClientInfoByUserToken)
        {

            string message = msg.Result;

            await SendAsync(webSocket, new { type = "length", length = message.Length });

            switch (message)
            {
                // 心跳
                case string m when m.StartsWith("ixpe_heartCheck:"):

                    if (WebSocketApplication.ClientsPool.ContainsKey(webSocket))
                        WebSocketApplication.ClientsPool[webSocket].EndHeartTime = DateTime.Now;
                    
                    await SendAsync(webSocket, new { type = "heart", date = m });
                    return;

                // 用户登录
                case string m when m.StartsWith("ixpe_userlogin:"):

                    try
                    {
                        var info = m.Substring(15, m.Length - 15);

                        var tuple = getClientInfoByUserToken.Invoke(info, webSocket);

                        if (tuple.Item2)
                        {
                            var clients = WebSocketApplication.ClientsPool
                                .Where(a =>
                                    a.Value.Project.ProjectToken == tuple.Item1.Project.ProjectToken
                                    && a.Value.User.Key == tuple.Item1.User.Key);

                            await SendAsync(webSocket, new { type = "clients", date = clients });

                            WebSocketApplication.ClientsPool.TryAdd(webSocket, tuple.Item1);

                        }
                        else
                        {
                            await SendAsync(webSocket, new { type = "userlogin", date = "登陆失败" });
                            webSocket.Dispose();
                        }

                    }
                    catch (Exception e)
                    {
                        await SendAsync(webSocket, new { type = "userlogin", date = e.Message });
                        webSocket.Dispose();
                    }

                    return;

            }

        }

    }
}
